﻿using System;
using System.Diagnostics;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Reflection;
using FluentSharp;
using FluentSharp.CoreLib;
using System.Yaml;
using System.Yaml.Serialization;
using System.Threading;
using System.Net;
using System.Web;



namespace BaseServer
{
  class Program
  {
    public class callback
    {
      public string response { get; set; }
      public int responsetime { get; set; }
    }

    const string CONFIG_FILE = "config.yaml";
    private static YamlConfig yc = new YamlConfig() { DontUseVerbatimTag = false, OmitTagForRootNode = false };

    public static Config Config = new Config();

    static private string WorkingDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
    static private string localConfigPath = WorkingDirectory.pathCombine(CONFIG_FILE);

    static private void WriteAndLoadDefaultConfig()
    {
      YamlSerializer ys = new YamlSerializer(yc);

      Config.Main.Directory = WorkingDirectory;
      Config.Main.Port = 23232;
      Config.Scripting.RootDirectory = Config.Main.Directory.pathCombine("scripts");
      Config.Scripting.InitScript = "init.cs";
      Config.Scripting.TickScript = "tick.cs";
      //Config.Scripting.ScriptProvider = BaseServer.Config.ScriptProvider.CSSCRIPT;

      ys.SerializeToFile(localConfigPath, Config);
    }

    static public int threadCount = 0;

    //static public Dictionary<string, callback> callbacks = new Dictionary<string, callback>();

    private static dynamic csTickContext;
    static void OnElapsed(object sender, System.Timers.ElapsedEventArgs e)
    {
      ((System.Timers.Timer)sender).Stop();

      try
      {
        if (csTickContext != null)
        {
          try
          {
            csTickContext.main();
          }
          catch (Exception ex)
          {

          }
        }
        if (Helper.Objects.ContainsKey("tick"))
          Console.WriteLine(Helper.Objects["tick"].ToString());
      }
      catch { }
      ((System.Timers.Timer)sender).Start();
    }

    private static System.Timers.Timer t = new System.Timers.Timer(100);
    static bool ready = false;

    static void Main(string[] args)
    {
      t.Elapsed += OnElapsed;
      #region "config"
      {
        foreach (string arg in Environment.GetCommandLineArgs())
          if (arg.StartsWith("-c:"))
          {
            string tmpConfigPath = arg.replace("-c:", "");
            if (Path.IsPathRooted(tmpConfigPath))
            {
              if (File.Exists(tmpConfigPath))
                localConfigPath = tmpConfigPath;
            }
            else
            {
              if (File.Exists(WorkingDirectory.pathCombine(tmpConfigPath)))
                localConfigPath = WorkingDirectory.pathCombine(tmpConfigPath);
            }
          }

        if (WorkingDirectory.directoryInfo().GetFiles(Path.GetFileName(localConfigPath)).Count() < 1)
        {
          WriteAndLoadDefaultConfig();
        }
        else
        {
          try
          {
            YamlSerializer ys = new YamlSerializer(yc);
            Config = (Config)ys.DeserializeFromFile(localConfigPath)[0];
          }
          catch (Exception ex)
          {
            ex.log();
            WriteAndLoadDefaultConfig();
          }
        }
      }
      #endregion

      Config.Main.Directory = Config.Main.Directory == null ? WorkingDirectory : Config.Main.Directory;
      Config.Scripting.RootDirectory = Config.Scripting.RootDirectory == null ? Config.Main.Directory.pathCombine("scripts") : Config.Scripting.RootDirectory;

      if (!System.IO.Directory.Exists(Config.Main.Directory))
        System.IO.Directory.CreateDirectory(Config.Main.Directory);

      Config.Scripting.RootDirectory = Path.IsPathRooted(Config.Scripting.RootDirectory) ? Config.Scripting.RootDirectory : Config.Main.Directory.pathCombine(Config.Scripting.RootDirectory);

      if (!System.IO.Directory.Exists(Config.Scripting.RootDirectory))
        System.IO.Directory.CreateDirectory(Config.Scripting.RootDirectory);

      string InitScriptPath;
      if (Path.IsPathRooted(Program.Config.Scripting.InitScript))
      {
        InitScriptPath = Program.Config.Scripting.InitScript;
      }
      else
      {
        InitScriptPath = Program.Config.Scripting.RootDirectory.pathCombine(Program.Config.Scripting.InitScript);
      }


      string script;

      dynamic csInitContext;
      try
      {
        csInitContext = CSScriptLibrary.CSScript.Evaluator.LoadFile(InitScriptPath);
      }
      catch (FileNotFoundException ex)
      {
        using (StreamWriter sw = new StreamWriter(InitScriptPath))
        {
          script =
@"using System;
using System.Dynamic;
using BaseServer;
using System.Collections.Generic;
public class Script
{
  // the init script must return an integer
	public int main()
	{
		
	  return 0;
  }
}".Replace("$q", "\"");
          sw.WriteLine(script);
          sw.Flush();
          csInitContext = CSScriptLibrary.CSScript.Evaluator.LoadCode(script);
        }
      }
      catch (Exception ex)
      {
        Console.WriteLine(ex.Message + Environment.NewLine + ex.StackTrace);
        csInitContext = null;
      }

      int Status;
      if (csInitContext != null)
      {
        Status = csInitContext.main();
      }

      string TickScriptPath;
      if (Path.IsPathRooted(Program.Config.Scripting.TickScript))
      {
        TickScriptPath = Program.Config.Scripting.TickScript;
      }
      else
      {
        TickScriptPath = Program.Config.Scripting.RootDirectory.pathCombine(Program.Config.Scripting.TickScript);
      }


      string tickScript;

      try
      {
        csTickContext = CSScriptLibrary.CSScript.Evaluator.LoadFile(TickScriptPath);
      }
      catch (FileNotFoundException ex)
      {
        using (StreamWriter sw = new StreamWriter(TickScriptPath))
        {
          tickScript =
@"using System;
using System.Dynamic;
using BaseServer;
using System.Collections.Generic;
public class Script
{
  // The tick script should not return anything
	public void main()
	{
		
	  return ;
  }
}".Replace("$q", "\"");
          sw.WriteLine(tickScript);
          sw.Flush();
          csTickContext = CSScriptLibrary.CSScript.Evaluator.LoadCode(tickScript);
        }
      }
      catch (Exception ex)
      {
        Console.WriteLine(ex.Message + Environment.NewLine + ex.StackTrace);
        csTickContext = null;
      }

      new Thread(MainThread).Start();

      while (!ready) { }

      string line = "";

      while (line.ToLower() != "exit")
      {
        Console.WriteLine("");
        Console.Write("Call: ");
        line = Console.ReadLine();
        if ((line.ToLower() != "exit") && !string.IsNullOrWhiteSpace(line))
          try
          {
            /*Console.WriteLine(*/
            new WebClient().DownloadString("http://localhost:" + Config.Main.Port.ToString() + "/" + line)/*)*/;
          }
          catch (Exception ex)
          {
            Console.WriteLine("Error");
          }

      }
      System.Environment.Exit(0);
    }

    public static void MainThread()
    {
      t.Start();

      HttpListener listener = new HttpListener();
      listener.Prefixes.Add("http://*:" + Config.Main.Port.ToString() + "/");
      listener.Start();
      Console.WriteLine("Listening on port " + Config.Main.Port.ToString() + "...");
      Program.ready = true;
      for (; ; )
      {
        HttpListenerContext ctx = listener.GetContext();
        if (threadCount < 200)
        {
          Interlocked.Increment(ref threadCount);
          if (!ctx.Request.Url.ToString().EndsWith("callbacks") && !ctx.Request.Url.ToString().EndsWith("favicon.ico"))
          {
            Console.WriteLine(ctx.Request.Url.ToString());
            Console.WriteLine("");
            Console.Write("Call: ");
          }
          new Thread(new Worker(ctx).ProcessRequest).Start();
        }
        else
        {
          byte[] b = Encoding.UTF8.GetBytes("Too much pending querys!");
          ctx.Response.ContentLength64 = b.Length;
          ctx.Response.AddHeader("Access-Control-Allow-Origin", "http://localhost:8383");
          ctx.Response.AddHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS, PUT, PATCH, DELETE");
          ctx.Response.AddHeader("Access-Control-Allow-Headers", "X-Requested-With,content-type");
          ctx.Response.AddHeader("Access-Control-Allow-Credentials", "true");
          ctx.Response.OutputStream.Write(b, 0, b.Length);
          ctx.Response.OutputStream.Close();
        }
      }
    }
  }

  public class Helper
  {
    public static Dictionary<string, dynamic> Objects = new Dictionary<string, dynamic>();
    public static dynamic FromJson(string JsonString)
    {
      return Newtonsoft.Json.JsonConvert.DeserializeObject(JsonString);
    }
    public static string ToJson(object obj)
    {
      return Newtonsoft.Json.JsonConvert.SerializeObject(obj);
    }
    public static void Show(string Message)
    {
      System.Windows.Forms.MessageBox.Show(Message);
    }
    public static string GetParameter(string Query, string ParameterName)
    {
      string Result = HttpUtility.ParseQueryString(Query).Get(ParameterName);
      if (Result == null)
        Result = "";
      return Result;
    }

    public static string CheckVariable(string variable)
    {
      if (variable == null)
        return "";
      else
        return variable;
    }
  }

  class Worker
  {
    private HttpListenerContext context;

    public Worker(HttpListenerContext context)
    {
      this.context = context;
    }



    public void ProcessRequest()
    {
      byte[] b;
      StringBuilder sb = new StringBuilder();
      try
      {
        string action = Helper.GetParameter(context.Request.Url.Query, "action");

        // Get path components. Trailing separators. Returns { "/", "func2/", "sunFunc2" }.
        List<string> pathsegments = new List<string>(context.Request.Url.Segments);

        pathsegments.RemoveAt(0);

        if (!context.Request.RawUrl.EndsWith("favicon.ico"))
        {





          // Get query string. Returns "?query=somevalue".
          string querystring = context.Request.Url.Query;

          string response = "";

          // Initialize a context

          string path;
          if (Path.IsPathRooted(Program.Config.Scripting.RootDirectory))
          {
            path = Program.Config.Scripting.RootDirectory.pathCombine(System.IO.Path.Combine(pathsegments.ToArray()));
          }
          else
          {
            path = Program.Config.Main.Directory.pathCombine(Program.Config.Scripting.RootDirectory).pathCombine(System.IO.Path.Combine(pathsegments.ToArray()));
          }

          string scriptPath = System.IO.Path.Combine(path, action) + ".cs";
          string script;

          if (!string.IsNullOrWhiteSpace(action))
          {
            if (!System.IO.Directory.Exists(path))
              System.IO.Directory.CreateDirectory(path);
          }
          else
            if (!System.IO.Directory.Exists(Path.GetDirectoryName(scriptPath)))
              System.IO.Directory.CreateDirectory(Path.GetDirectoryName(scriptPath));

          dynamic csContext;
          try
          {
            csContext = CSScriptLibrary.CSScript.Evaluator.LoadFile(scriptPath);
          }
          catch (FileNotFoundException ex)
          {
            using (StreamWriter sw = new StreamWriter(scriptPath))
            {
              script =
@"// These using directives must be present in a file
using System;
using System.Dynamic;
using BaseServer;
using System.Collections.Generic;

// The file must contain exactly one class. this class has the name script and must be public.
public class Script
{
	// The class must contain $qpublic string query;$q
	public string query;

  // The class can contain $qpublic System.Net.HttpListenerRequest request;$q
  public System.Net.HttpListenerRequest request;
	
	// the entry point for the script must be named main, without parameters and return a string (response)
	public string main()
	{		
		// Helper.Objects is a shared resource between all scripts
		// Lock for thread safety
		lock (Helper.Objects)
		{
			// Helper.GetParameter(query, $qobj$q) extracts the value of the parameter $qobj$q from the query string
			// Helper.FromJson() converts a json string to a dynamic object
      try
      {
			  Helper.Objects[$qConfig$q][(string)Helper.FromJson(Helper.GetParameter(query, $qobj$q)).key] = Helper.FromJson(Helper.GetParameter(query, $qobj$q)).value;
      }
			catch
			{}
		}
		
		// dynamic keyword + ExpandoObject = lots of fun
		// add properties on the fly, without defining them
		dynamic response = new ExpandoObject();
		// add properties on the fly, without defining them
		response.state = $qOK$q;
		
		// add ExpandoObjects on the fly, for nested fun
		response.obj = new ExpandoObject();
    try
    {
		  response.obj.id = Helper.FromJson(Helper.GetParameter(query, $qobj$q)).id != null ? Helper.FromJson(Helper.GetParameter(query, $qobj$q)).id : -1;
    }
		catch {}
	// Helper.ToJson(response) converts the response to a json string
	return Helper.ToJson(response);
  }
}
".Replace("$q", "\"");
              sw.WriteLine(script);
              sw.Flush();
              csContext = CSScriptLibrary.CSScript.Evaluator.LoadCode(script);
            }
          }
          catch (Exception ex)
          {
            Console.WriteLine(ex.Message + Environment.NewLine + ex.StackTrace);
            b = Encoding.UTF8.GetBytes("");
            if (!context.Request.Url.ToString().EndsWith("callbacks") && !context.Request.Url.ToString().EndsWith("favicon.ico"))
              Console.WriteLine(sb.ToString());
            Console.WriteLine("");
            Console.Write("Call: ");
            context.Response.AddHeader("Access-Control-Allow-Origin", "http://localhost:8383");
            context.Response.AddHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS, PUT, PATCH, DELETE");
            context.Response.AddHeader("Access-Control-Allow-Headers", "X-Requested-With,content-type");
            context.Response.AddHeader("Access-Control-Allow-Credentials", "true");
            context.Response.ContentLength64 = b.Length;
            context.Response.OutputStream.Write(b, 0, b.Length);
            context.Response.OutputStream.Close();
            return;
          }


          // Setting external parameters for the context

          csContext.query = context.Request.Url.Query;


          try
          {
            csContext.url = context.Request.Url.ToString();
          }
          catch { }

          try
          {
            csContext.request = context.Request;
          }
          catch { }
          try
          {
            response = csContext.main();
          }
          catch (Exception ex)
          {
            response = "";

            Console.WriteLine(ex.Message);
            Console.WriteLine("");
            Console.WriteLine(ex.StackTrace);
            Console.WriteLine("");
            Console.Write("Call: ");
          }
          sb.Append(response.ToString());
        }


      }
      catch (Exception ex)
      {
        Console.WriteLine(ex.Message);
        Console.WriteLine("");
        Console.WriteLine(ex.StackTrace);
      }
      b = Encoding.UTF8.GetBytes(sb.ToString());
      if (!context.Request.Url.ToString().EndsWith("callbacks") && !context.Request.Url.ToString().EndsWith("favicon.ico"))
      {
        Console.WriteLine(sb.ToString());
        Console.WriteLine("");
        Console.Write("Call: ");
      }

      context.Response.AddHeader("Access-Control-Allow-Origin", "http://localhost:8383");
      context.Response.AddHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS, PUT, PATCH, DELETE");
      context.Response.AddHeader("Access-Control-Allow-Headers", "X-Requested-With,content-type");
      context.Response.AddHeader("Access-Control-Allow-Credentials", "true");
      context.Response.ContentLength64 = b.Length;
      context.Response.OutputStream.Write(b, 0, b.Length);
      context.Response.OutputStream.Close();
      Interlocked.Decrement(ref Program.threadCount);
    }
  }

}
